#include "xl9555.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>

static i2c_master_bus_handle_t xl9555_i2c_master = NULL;
static i2c_master_dev_handle_t xl9555_i2c_device  =NULL;
static xl9555_input_cb_t    xl9555_input_callback = NULL;
static gpio_num_t xl9555_int_io = GPIO_NUM_NC;
static uint16_t xl9555_io_config = 0xFFFF;
static const char * xl9555_name = "xl9555";

/**
 * @brief       读取XL9555的16位IO值
 * @param       data：读取数据的存储区
 * @param       len：读取数据的大小
 * @retval      ESP_OK：读取成功；其他：读取失败
 */
esp_err_t xl9555_read_byte(uint8_t *data, size_t len)
{
    uint8_t memaddr_buf[1];
    memaddr_buf[0]  = XL9555_INPUT_PORT0_REG;
    return i2c_master_transmit_receive(xl9555_i2c_device, &memaddr_buf[0], 1, data, len, 500);
}

/**
 * @brief       向XL9555写入16位IO值
 * @param       reg：寄存器地址
 * @param       data：要写入的数据
 * @param       len：要写入数据的大小
 * @retval      ESP_OK：读取成功；其他：读取失败
 */
esp_err_t xl9555_write_byte(uint8_t reg, uint8_t *data, size_t len)
{
    esp_err_t ret;
    uint8_t *write_buf = (uint8_t*)malloc(len + 1);
    if(!write_buf)
        return ESP_FAIL;
    write_buf[0] = reg;
    memcpy(&write_buf[1],data,len);

    ret = i2c_master_transmit(xl9555_i2c_device, write_buf, len + 1, 500);
    return ret;
}

/**
 * @brief       控制某个IO的电平
 * @param       pin     : 控制的IO
 * @param       val     : 电平
 * @retval      返回所有IO状态
 */
uint16_t xl9555_pin_write(uint16_t pin, int val)
{
    uint8_t w_data[2];
    uint16_t temp = 0x0000;

    xl9555_read_byte(w_data, 2);

    if (pin <= IO0_7)
    {
        if (val)
        {
            w_data[0] |= (uint8_t)(0xFF & pin);
        }
        else
        {
            w_data[0] &= ~(uint8_t)(0xFF & pin);
        }
    }
    else
    {
        if (val)
        {
            w_data[1] |= (uint8_t)(0xFF & (pin >> 8));
        }
        else
        {
            w_data[1] &= ~(uint8_t)(0xFF & (pin >> 8));
        }
    }

    temp = ((uint16_t)w_data[1] << 8) | w_data[0]; 

    xl9555_write_byte(XL9555_OUTPUT_PORT0_REG, w_data, 2);

    return temp;
}

/**
 * @brief       获取某个IO状态
 * @param       pin     : 要获取状态的IO
 * @retval      此IO口的值(状态, 0/1)
 */
int xl9555_pin_read(uint16_t pin)
{
    uint16_t ret;
    uint8_t r_data[2];

    xl9555_read_byte(r_data, 2);

    ret = r_data[1] << 8 | r_data[0];

    return (ret & pin) ? 1 : 0;
}

/**
 * @brief       XL9555的IO配置
 * @param       config_value：IO配置输入或者输出
 * @retval      返回设置的数值
 */
void xl9555_ioconfig(uint16_t config_value)
{
    /* 从机地址 + CMD + data1(P0) + data2(P1) */
    /* P00、P01、P04、P06为输入，其他引脚为输出 -->0000 0000 0101 0011 注意：0为输出，1为输入*/
    uint8_t data[2];
    esp_err_t err;

    data[0] = (uint8_t)(0xFF & config_value);
    data[1] = (uint8_t)(0xFF & (config_value >> 8));
    xl9555_io_config = config_value;
    do
    {
        err = xl9555_write_byte(XL9555_CONFIG_PORT0_REG, data, 2);

        if (err != ESP_OK)
        {
            ESP_LOGE(xl9555_name, "%s configure %X failed, ret: %d", __func__, config_value, err);
        }
        
        vTaskDelay(pdMS_TO_TICKS(100));
        
    } while (err != ESP_OK);
}

/**
 * @brief       外部中断服务函数
 * @param       arg：中断引脚号
 * @note        IRAM_ATTR: 这里的IRAM_ATTR属性用于将中断处理函数存储在内部RAM中，目的在于减少延迟
 * @retval      无
 */
static void IRAM_ATTR xl9555_exit_gpio_isr_handler(void *arg)
{
    uint32_t gpio_num = (uint32_t) arg;
    
    if (gpio_num == xl9555_int_io)
    {
        esp_rom_delay_us(20000);

        if ( gpio_get_level(xl9555_int_io) == 0)
        {
            /* 中断处理 */
        }
    }
}

/**
 * @brief       外部中断初始化程序
 * @param       无
 * @retval      无
 */
static void xl9555_int_init(void)
{
    gpio_config_t gpio_init_struct;

    /* 配置BOOT引脚和外部中断 */
    gpio_init_struct.mode = GPIO_MODE_INPUT;                    /* 选择为输入模式 */
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;           /* 上拉使能 */
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;      /* 下拉失能 */
    gpio_init_struct.intr_type = GPIO_INTR_NEGEDGE;             /* 下降沿触发 */
    gpio_init_struct.pin_bit_mask = 1ull << xl9555_int_io;      /* 设置的引脚的位掩码 */
    gpio_config(&gpio_init_struct);                             /* 配置使能 */
    
    /* 注册中断服务 */
    gpio_install_isr_service(0);
    
    /* 设置GPIO的中断回调函数 */
    gpio_isr_handler_add(xl9555_int_io, xl9555_exit_gpio_isr_handler, (void*) xl9555_int_io);
}

static void xl9555_intput_scan(void* param)
{
    esp_err_t ret;
    while(1)
    {
        uint8_t input[2];
        ret = xl9555_read_byte(&input[0],2);
        if(ret == ESP_OK)
        {
            for(int i = 0;i < 16;i++)
            {
                if(input[i<=7?0:1]&(1<<(i&7)))
                {
                    if(xl9555_io_config & (1 <<i))
                    {
                        if(xl9555_input_callback)
                        {
                            xl9555_input_callback(1<<i);
                        }
                    }
                }
            }
        }
        vTaskDelay(pdMS_TO_TICKS(40));
    }
}

/**
 * @brief       初始化XL9555
 * @param       self : i2c_obj_t句柄
 * @retval      无
 */
void xl9555_init(i2c_master_bus_handle_t bus_handle,gpio_num_t int_io,xl9555_input_cb_t f)
{
    uint8_t r_data[2];
    i2c_device_config_t dev_config = 
    {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = XL9555_ADDR,
        .scl_speed_hz = 100000,
    };
    xl9555_input_callback = f;
    xl9555_int_io = int_io;
    xl9555_i2c_master = bus_handle;
    i2c_master_bus_add_device(xl9555_i2c_master, &dev_config, &xl9555_i2c_device);

    /* 上电先读取一次清除中断标志 */
    xl9555_read_byte(r_data, 2);

    if(xl9555_int_io != GPIO_NUM_NC)
        xl9555_int_init();
    //xTaskCreatePinnedToCore(xl9555_intput_scan,"xl9555",4096,NULL,3,NULL,1);
}
